/*
Copyright 2023 cloudea.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controllers

import (
	"context"
	"fmt"
	"reflect"
	"strconv"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/runtime"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
	"sigs.k8s.io/controller-runtime/pkg/log"

	k8simooccomv1alpha1 "github.com/imooc-com/imoocpod-operator/api/v1alpha1"
)

// ImoocPodReconciler reconciles a ImoocPod object
type ImoocPodReconciler struct {
	client.Client
	Scheme *runtime.Scheme
}

//+kubebuilder:rbac:groups=k8s.imooc.com.k8s.imooc.com,resources=imoocpods,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=k8s.imooc.com.k8s.imooc.com,resources=imoocpods/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=k8s.imooc.com.k8s.imooc.com,resources=imoocpods/finalizers,verbs=update
// 增加操作pod的权限
//+kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups="",resources=pods/status,verbs=get

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the ImoocPod object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.1/pkg/reconcile
func (r *ImoocPodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	logger := log.FromContext(ctx)
	logger.Info("reconcile start")

	// your logic here
	// 1. 获取name对应的所有pod列表
	// 2. 多了删除，少了增加

	logger.Info("1. 获取当前实例")
	instance := &k8simooccomv1alpha1.ImoocPod{}
	if err := r.Client.Get(ctx, req.NamespacedName, instance); err != nil {
		if errors.IsNotFound(err) {
			return ctrl.Result{}, nil // 说明已经被删除了，不需要再调度
		}
		return ctrl.Result{}, err
	}

	// OnAdd，和OnUpdate的逻辑是一样的，都是根据当前状态和期望状态做比较，然后进行某些操作。

	logger.Info("2. 获取当前实例的Pod列表")
	lbls := labels.Set{
		"app": instance.Name,
	}
	existingPods := &corev1.PodList{}
	err := r.Client.List(ctx, existingPods, &client.ListOptions{
		Namespace:     req.Namespace,
		LabelSelector: labels.SelectorFromSet(lbls),
	})
	if err != nil {
		logger.Error(err, "取已存在的Pod失败")
		return ctrl.Result{}, err
	}

	logger.Info("3. Pod列表的名字")
	var existPodNames []string
	for _, pod := range existingPods.Items {
		if pod.GetObjectMeta().GetDeletionTimestamp() != nil {
			continue
		}
		if pod.Status.Phase == corev1.PodPending || pod.Status.Phase == corev1.PodRunning {
			// 如果是pending或running
			existPodNames = append(existPodNames, pod.GetObjectMeta().GetName())
		}
	}

	// 比较期望与实际
	logger.Info("4. 更新实例的实际状态")
	status := k8simooccomv1alpha1.ImoocPodStatus{
		PodNames: existPodNames,
	}
	if !reflect.DeepEqual(instance.Status, status) {
		instance.Status = status
		err := r.Client.Status().Update(ctx, instance)
		if err != nil {
			logger.Error(err, "更新Pod失败")
			return ctrl.Result{}, err
		}
	}

	// 进行缩放
	if len(existPodNames) > instance.Spec.Replicas {
		// delete
		logger.Info("5. 缩小")
		logger.Info(fmt.Sprintf("正在删除Pod：当前%d ， 期望 %d", len(existPodNames), instance.Spec.Replicas))
		pod := existingPods.Items[0]
		err := r.Client.Delete(ctx, &pod)
		if err != nil {
			logger.Error(err, "删除Pod失败")
			return ctrl.Result{}, err
		}
	}

	if len(existPodNames) < instance.Spec.Replicas {
		logger.Info("5. 放大")
		logger.Info(fmt.Sprintf("正在增加Pod：当前%d ， 期望 %d", len(existPodNames), instance.Spec.Replicas))
		// create
		pod := newPodForCR(instance)
		// ControllerReferernce
		// OwerReference
		if err := controllerutil.SetControllerReference(instance, pod, r.Scheme); err != nil {
			logger.Error(err, "创建Pod引用失败")
			return ctrl.Result{}, err
		}

		if err := r.Client.Create(ctx, pod); err != nil {
			logger.Error(err, "创建Pod失败")
			return ctrl.Result{}, err
		}

	}

	if len(existPodNames) == instance.Spec.Replicas {
		logger.Info("与期望一样，无需更改")
		return ctrl.Result{Requeue: true}, nil
	}

	return ctrl.Result{Requeue: true}, nil
}

var id = 0

func newPodForCR(cr *k8simooccomv1alpha1.ImoocPod) *corev1.Pod {
	labels := map[string]string{
		"app": cr.GetName(),
	}
	id += 1
	return &corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:      cr.Name + "-pod-" + strconv.Itoa(id),
			Namespace: cr.Namespace,
			Labels:    labels,
		},
		Spec: corev1.PodSpec{
			Containers: []corev1.Container{
				{
					Name:    "busybox",
					Image:   "busybox",
					Command: []string{"sleep", "3600"},
				},
			},
		},
	}
}

// SetupWithManager sets up the controller with the Manager.
func (r *ImoocPodReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&k8simooccomv1alpha1.ImoocPod{}).
		Complete(r)
}
